# ICEMGenerator - 高效的ICEM CFD网格划分Replay Script生成工具
# 模块： Point类，用于创建点
# 作者： weixc
# 邮箱： weixinchong [at] 163.com
# 项目主页： https://gitee.com/weixinchong/icemgenerator

import numpy as np
from collections.abc import Iterable
from .param_check import ListSizeError

class Point():
    '''创建一个点'''

    curr_pId = 0  #类静态变量，用于确定最新创建点的编号
    members = []  #现有点的成员列表

    def init_pId(pId: int):
        '''初始化最新创建点的编号，应在开始创建点之前调用
        :param pId: 创建的第一个点的编号'''
        Point.curr_pId = pId

    def create(x: float, y: float, z: float, name: str = None):
        '''创建一个点（已存在则引用，不存在则创建）
        :params x, y, z: 点的坐标
        :param name: 点的名称，非必须
        :return (Point): 创建的点'''
        pnt = Point.find_member(x, y, z)
        if pnt == None:
            pnt = Point(x, y, z, name)
        elif name != None:
            pnt.setName(name)
        return pnt

    def members_replay() -> str:
        '''生成所有点的Replay Script
        :return: 生成的Replay Script'''
        text = ''
        for p in Point.members:
            text += p.replay() + '\n'
        return text

    def find_member(x: float, y: float, z: float):
        '''查找指定坐标的点是否已经定义过
        :params x, y, z: 要查找的点的坐标
        :return (Point | None): 找到已定义的点时，返回该点；否则为None'''
        for p in Point.members:
            if p.X[0] == x and p.X[1] == y and p.X[2] == z:
                return p

    def get_repair_tolerance() -> float:
        '''根据几何模型的空间尺度范围，确定修复几何时的公差
        :return 公差值'''
        x_values = []
        y_values = []
        z_values = []
        for p in Point.members:
            x_values.append(p.X[0])
            y_values.append(p.X[1])
            z_values.append(p.X[2])
        x_range = np.max(x_values) - np.min(x_values)
        y_range = np.max(y_values) - np.min(y_values)
        z_range = np.max(z_values) - np.min(z_values)
        tolerance = max(x_range, y_range, z_range) / 2500
        return tolerance

    def __init__(self, x, y, z, name):
        '''根据坐标(x,y,z)创建点'''
        self.pId = Point.curr_pId
        if x != 0 and abs(x) < 1e-12:
            x = 0
        if y != 0 and abs(y) < 1e-12:
            y = 0
        if z != 0 and abs(z) < 1e-12:
            z = 0
        self.X = np.array([x, y, z])
        self.name = name
        Point.members.append(self)  #将当前点增加到成员列表
        Point.curr_pId += 1  #最新点编号增加1

    def __str__(self):
        return 'pnt.%03d' % self.pId

    def __repr__(self):
        return self.__str__()

    def setName(self, name: str):
        '''更改点的名称
        :param name: 要设定的点名称'''
        self.name = name

    def replay(self) -> str:
        '''生成点的Replay Script
        :return: 生成的Replay Script'''
        return 'ic_point {} GEOM %s %g,%g,%g' % (self, self.X[0], self.X[1], self.X[2])

    def info(self) -> str:
        '''获得点的信息
        :return：点的信息'''
        text = 'Point %03d' % self.pId
        if self.name != None:
            text += ' [%s]' % self.name
        text += ': (%g,%g,%g)' % (self.X[0], self.X[1], self.X[2])
        return text

def create_points(x: float | Iterable, y: float | Iterable, z: float | Iterable = None) -> tuple:
    '''根据坐标值创建一系列点
    :params x,y,z: 对于x、y、z，应有不少于1个变量为列表，对于二维模型，参数`z`可以为空，例如:
        (1)x=(1,3,6), y=3: 创建一系列点(1,3,0),(3,3,0),(6,3,0)；
        (2)x=(1,3,6), y=(1,2,3), z=(8,7,6): 创建一系列点(1,1,8),(3,2,7),(6,3,6)
    :returns: 一系列点组成的元组'''
    points = []
    # 获得要创建的点个数
    if isinstance(x, Iterable):
        nc = len(x)
    elif isinstance(y, Iterable):
        nc = len(y)
    elif isinstance(z, Iterable):
        nc = len(z)
    else:
        raise ListSizeError('创建一系列点时，参数x,y,z至少有一个是列表')
    # 检查坐标值个数是否一致
    if isinstance(y, Iterable) and len(y) != nc:
        raise ListSizeError('参数x,y,z的坐标值个数应保持一致')
    elif isinstance(z, Iterable) and len(z) != nc:
        raise ListSizeError('参数x,y,z的坐标值个数应保持一致')
    # 将单一值的参数拓展成列表
    if not isinstance(x, Iterable):
        x = [x] * nc
    if not isinstance(y, Iterable):
        y = [y] * nc
    if z is None:
        z = [0] * nc
    else:
        if not isinstance(z, Iterable):
            z = [z] * nc
    # 开始创建点
    for i in range(nc):
        points.append(Point.create(x[i], y[i], z[i]))
    return tuple(points)